#include "stdafx.h"

#include "bochs.h"

extern float_status_t FPU_pre_exception_handling(Bit16u control_word);

#include "softfloatx80.h"

static int status_word_flags_fpu_compare(int float_relation)
{
  switch(float_relation) {
     case float_relation_unordered:
         return (FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);

     case float_relation_greater:
         return (0);

     case float_relation_less:
         return (FPU_SW_C0);

     case float_relation_equal:
         return (FPU_SW_C3);
  }

  return (-1);	// should never get here
}

void IA32_CPU::write_eflags_fpu_compare(int float_relation)
{
  switch(float_relation) {
   case float_relation_unordered:
      IA32_setEFlagsOSZAPC(EFlagsZFMask | EFlagsPFMask | EFlagsCFMask);
      break;

   case float_relation_greater:
      IA32_setEFlagsOSZAPC(0);
      break;

   case float_relation_less:
      IA32_setEFlagsOSZAPC(EFlagsCFMask);
      break;

   case float_relation_equal:
      IA32_setEFlagsOSZAPC(EFlagsZFMask);
      break;

   default:
	  break;
  }
}

void IA32_CPU::FCOM_STi(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->nnn() & 1;

  clear_C1();

  if (IS_TAG_EMPTY(0) || IS_TAG_EMPTY(i->rm()))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), IA32_READ_FPU_REG(i->rm()), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FCOMI_ST0_STj(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->b1() & 4;

  clear_C1();

  if (IS_TAG_EMPTY(0) || IS_TAG_EMPTY(i->rm()))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          IA32_setEFlagsOSZAPC(EFlagsZFMask | EFlagsPFMask | EFlagsCFMask);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), IA32_READ_FPU_REG(i->rm()), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

   write_eflags_fpu_compare(rc);

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FUCOMI_ST0_STj(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->b1() & 4;

  clear_C1();

  if (IS_TAG_EMPTY(0) || IS_TAG_EMPTY(i->rm()))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          IA32_setEFlagsOSZAPC(EFlagsZFMask | EFlagsPFMask | EFlagsCFMask);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare_quiet(IA32_READ_FPU_REG(0), IA32_READ_FPU_REG(i->rm()), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

   write_eflags_fpu_compare(rc);

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FUCOM_STi(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->nnn() & 1;

  if (IS_TAG_EMPTY(0) || IS_TAG_EMPTY(i->rm()))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare_quiet(IA32_READ_FPU_REG(0), IA32_READ_FPU_REG(i->rm()), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FCOM_SINGLE_REAL(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->nnn() & 1;

  clear_C1();

  if (IS_TAG_EMPTY(0))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  float32 load_reg;
  read_virtual_dword(i->seg(), IA32_RMAddr(i), &load_reg);

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), 
  	float32_to_floatx80(load_reg, status), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FCOM_DOUBLE_REAL(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->nnn() & 1;

  clear_C1();

  if (IS_TAG_EMPTY(0))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  float64 load_reg;
  read_virtual_qword(i->seg(), IA32_RMAddr(i), &load_reg);

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), 
  	float64_to_floatx80(load_reg, status), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FICOM_WORD_INTEGER(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->nnn() & 1;

  clear_C1();

  if (IS_TAG_EMPTY(0))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  Bit16s load_reg;
  read_virtual_word(i->seg(), IA32_RMAddr(i), (Bit16u*)(&load_reg));

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), 
  	int32_to_floatx80((Bit32s)(load_reg)), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

  if (pop_stack)
       the_i387.FPU_pop();
}

void IA32_CPU::FICOM_DWORD_INTEGER(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int pop_stack = i->nnn() & 1;

  clear_C1();

  if (IS_TAG_EMPTY(0))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
          if (pop_stack)
               the_i387.FPU_pop();
      }
      return;
  }

  Bit32s load_reg;
  read_virtual_dword(i->seg(), IA32_RMAddr(i), (Bit32u*)(&load_reg));

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), 
  	int32_to_floatx80(load_reg), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

  if (pop_stack)
       the_i387.FPU_pop();
}

/* DE D9 */
void IA32_CPU::FCOMPP(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  clear_C1();

  if (IS_TAG_EMPTY(0) || IS_TAG_EMPTY(1))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);

           the_i387.FPU_pop();
           the_i387.FPU_pop();
      }
      return;
  }

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), IA32_READ_FPU_REG(1), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

   the_i387.FPU_pop();
   the_i387.FPU_pop();
}

/* DA E9 */
void IA32_CPU::FUCOMPP(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  if (IS_TAG_EMPTY(0) || IS_TAG_EMPTY(1))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);

           the_i387.FPU_pop();
           the_i387.FPU_pop();
      }
      return;
  }

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare_quiet(IA32_READ_FPU_REG(0), IA32_READ_FPU_REG(1), status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));

   the_i387.FPU_pop();
   the_i387.FPU_pop();
}

void IA32_CPU::FCMOV_ST0_STj(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  int st0_tag =  the_i387.FPU_gettagi(0);
  int sti_tag =  the_i387.FPU_gettagi(i->rm());

  if (st0_tag == FPU_Tag_Empty || sti_tag == FPU_Tag_Empty)
  {
      FPU_stack_underflow(0);
     return;
  }

  floatx80 sti_reg = IA32_READ_FPU_REG(i->rm());

  bx_bool condition = 0;
  switch(i->nnn())
  {
     case 0: condition = get_CF(); break;
     case 1: condition = get_ZF(); break;
     case 2: condition = get_CF() || get_ZF(); break;
     case 3: condition = get_PF(); break;
     default:
       break;
  }
  if (i->b1() & 1) 
     condition = !condition;

  if (condition)
     IA32_WRITE_FPU_REGISTER_AND_TAG(sti_reg, sti_tag, 0);
 
}

/* D9 E4 */
void IA32_CPU::FTST(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  clear_C1();

  if (IS_TAG_EMPTY(0))
  {
       FPU_exception(FPU_EX_Stack_Underflow);

      if( the_i387.is_IA_masked())
      {
          /* the masked response */
          setcc(FPU_SW_C0|FPU_SW_C2|FPU_SW_C3);
      }
      return;
  }

  extern const floatx80 Const_Z;

  float_status_t status = 
      FPU_pre_exception_handling( the_i387.get_control_word());

  int rc = floatx80_compare(IA32_READ_FPU_REG(0), Const_Z, status);

  if ( FPU_exception(status.float_exception_flags))
      return;

  setcc(status_word_flags_fpu_compare(rc));
}

/* D9 E5 */
void IA32_CPU::FXAM(Ia32_Instruction_c *i)
{
   prepareFPU(i);

  floatx80 reg = IA32_READ_FPU_REG(0);
  int sign = floatx80_sign(reg);

  if (IS_TAG_EMPTY(0))
  {
      setcc(FPU_SW_C3|FPU_SW_C1|FPU_SW_C0);
  }
  else
  {
      float_class_t aClass = floatx80_class(reg);

      switch(aClass)
      {
        case float_zero:
           setcc(FPU_SW_C3|FPU_SW_C1);
           break;
       
        case float_NaN:
           // unsupported handled as NaNs
           if (floatx80_is_unsupported(reg)) {
               setcc(FPU_SW_C1); 
           } else {
               setcc(FPU_SW_C1|FPU_SW_C0);
           }
           break;
       
        case float_negative_inf:
        case float_positive_inf:
           setcc(FPU_SW_C2|FPU_SW_C1|FPU_SW_C0);
           break;
       
        case float_denormal:
           setcc(FPU_SW_C3|FPU_SW_C2|FPU_SW_C1);
           break;
       
        case float_normalized:
           setcc(FPU_SW_C2|FPU_SW_C1);
           break;
      }
  }

  if (! sign)
    clear_C1();

}
